/**
 * @file calculator.c
 *
 */

/*********************
 *      INCLUDES
 *********************/

#include "lv_watch.h"

#if USE_LV_WATCH_CALCULATOR != 0

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*********************
 *      DEFINES
 *********************/
#define CALC_LATIN_SYMBOL_MUL_AND_DIV_SUP  1

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static lv_obj_t * calc_create(lv_obj_t * activity_obj);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/
#define CALC_PLUS              "+"
#define CALC_MINUS             "-"
#if CALC_LATIN_SYMBOL_MUL_AND_DIV_SUP != 0
#define CALC_DIV               "÷"
#define CALC_MUL               "×"
#else
#define CALC_DIV               "/"
#define CALC_MUL               "x"
#endif
#define CALC_EQUALS            "="

#define CALC_WARNING_TIME_OUT  1000

#define MAX_TOKEN_SIZE         14
#define EXT_TOKEN_SIZE         5
#define MAX_PRECISION          10
#define CALC_MAX_TEXT_SIZE     23 /*-xxxxxxxxxx.xxxxxxxxxx'\0'*/

/**********************
 *   GLOBAL FUNCTIONS
***********************/
void calculator_create_event_cb(lv_obj_t * btn, lv_event_t e)
{
    (void)btn;
    if(LV_EVENT_CLICKED == e) {
        lv_watch_png_cache_all_free();
        lv_obj_t * obj = calc_create(NULL);
        LV_ASSERT_MEM(obj);
    }
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static lv_calculator_obj_ext_t * calc_get_ext(void)
{
    lv_obj_t * activity_obj = lv_watch_get_activity_obj(ACT_ID_CALCULATOR);
    if(activity_obj == NULL) return NULL;

    lv_obj_t * watch_obj;
    lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &watch_obj);
    if(watch_obj == NULL) return NULL;

    if(watch_obj) {
        lv_calculator_obj_ext_t  * ext = lv_obj_get_ext_attr(watch_obj);
        return ext;

    }
    return NULL;
}

static void calc_label_set_text(lv_calculator_obj_ext_t * ext, char * text)
{
    lv_label_set_text(ext->label, text);
    lv_obj_align(ext->label, ext->page, LV_ALIGN_IN_RIGHT_MID, 0, 0);
}

static void calc_label_del_char(lv_calculator_obj_ext_t * ext)
{
    const char * text = lv_label_get_text(ext->label);
    uint8_t len = strlen(text);
    if(0 < len) {
#if CALC_LATIN_SYMBOL_MUL_AND_DIV_SUP != 0
        if (NULL != ext->oper)
        {
            if (strcmp(ext->oper, CALC_DIV) == 0
                || strcmp(ext->oper, CALC_MUL) == 0)
            {
                len--;
            }
        }
#endif
        lv_label_cut_text(ext->label, len - 1, 1);
        lv_obj_align(ext->label, ext->page, LV_ALIGN_IN_RIGHT_MID, 0, 0);
    }
}

static void calc_label_add_text(lv_calculator_obj_ext_t * ext, const char * text)
{
    lv_label_ins_text(ext->label, LV_LABEL_POS_LAST, text);
    lv_obj_align(ext->label, ext->page, LV_ALIGN_IN_RIGHT_MID, 0, 0);
}

static void calc_warn_task(lv_task_t * task)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(ext == NULL) return;

    lv_obj_set_hidden(ext->cont_warn, true);
    lv_task_del(ext->warn_task_p);
    ext->warn_task_p = NULL;
}

static void calc_add_oper(char * ptr)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(ext->oper) {
        if(ext->point == false && ext->input_len == 0) {
            calc_label_del_char(ext);
        } else
            return;
    } else {
        if(ext->input_len == 0) {
            if(ext->point) {
                calc_label_del_char(ext);
            } else {
                if(strcmp(ptr, CALC_MINUS) == 0) {
                    calc_label_add_text(ext, ptr);
                    ext->finish_flag = false;
                }
                return;
            }
        }
    }

    ext->oper = ptr;
    calc_label_add_text(ext, ptr);
    ext->finish_flag = false;
    ext->input_len = 0;
    ext->point = false;
}

static void calc_get_number_string(uint8_t * str, char * result)
{
    uint8_t start_index;
    uint8_t end_index;
    uint8_t i;

    for(i = 1; i < 10; i++) {
        if(0 < str[i]) {
            break;
        }
    }
    start_index = i;

    for(i = 20; i > 10; i--) {
        if(0 < str[i]) {
            break;
        }
    }
    end_index = i;

    uint8_t index = 0;
    for(i = start_index; i <= 10; i++) {
        result[index++] = str[i] + '0';
    }
    if(10 < end_index) {
        result[index++] = '.';
        for(; i <= end_index; i++) {
            result[index++] = str[i] + '0';
        }
    }
}

static void calc_get_number_pos(char * num, uint8_t * p_pos, uint8_t * e_pos, int8_t * e_num)
{
    uint8_t len = strlen(num);
    for(uint8_t i = len - 1; i > 0; i--) {
        if('.' == num[i]) {
            *p_pos = i;
            break;
        } else if((NULL != e_pos) && ('e' == num[i])) {
            *e_pos = i;
            *e_num = atof(&num[i + 1]);
        }
    }
}

static bool calc_copy_str_to_num(uint8_t * num, char * str)
{
    bool equal_to_zero = false;

    uint8_t len = strlen(str);
    uint8_t p_pos = len;
    uint8_t e_pos = len;
    int8_t e_num = 0;
    calc_get_number_pos(str, &p_pos, &e_pos, &e_num);

    if(10 == e_num && '1' == str[0]) {
        printf("%s: e_num equal to 10\n", __FUNCTION__);
        uint8_t index = 0;
        for(uint8_t i = 0; i < e_pos; i++) {
            if(1 == i) {
                /*decimal point*/
                continue;
            }
            num[index++] = str[i] - '0';
        }
    } else if(-11 == e_num) {
        printf("%s: e_num equal to -11\n", __FUNCTION__);
        if(5 <= str[0]) {
            /*round*/
            num[20] = 1;
        } else {
            /*equal to 0 after round*/
            equal_to_zero = true;
        }
    } else if(0 > e_num && -11 < e_num) {
        printf("%s: e_num is between 0 and -11\n", __FUNCTION__);
        uint8_t index = 0;
        for(uint8_t i = 10 - e_num; i < 21; i++) {
            num[i] = str[index++] - '0';
            if(e_pos == index) {
                break;
            } else if(1 == index) {
                /*decimal point*/
                index++;
            }
        }
    } else {
        uint8_t index = 11 - p_pos;
        for(uint8_t i = 0; i < p_pos; i++) {
            num[index++] = str[i] - '0';
        }
        if(len > p_pos) {
            index = 11;
            for(uint8_t i = p_pos + 1; i < len; i++) {
                num[index++] = str[i] - '0';
            }
        }
    }

    return equal_to_zero;
}

static void calc_addition(char * text, char * num1, char * num2, bool negative)
{
    if(NULL == text) return;

    if((0 == strcmp(num1, "0")) && (0 == strcmp(num2, "0"))) {
        text[0] = '0';
        return;
    }

    char * result = text;
    if(negative) {
        text[0] = '-';
        result = &text[1];
    }

    /*integer 1 - 10, decimal 11 - 20*/
    uint8_t str1[21] = {0};
    uint8_t str2[21] = {0};

    if(calc_copy_str_to_num(str1, num1)) {
        memcpy(result, num2, strlen(num2));
        return;
    }

    calc_copy_str_to_num(str2, num2);

    for(uint8_t i = 20; i > 0; i--) {
        if('0' <= str1[i]) {
            str1[i] -= '0';
        }
        if('0' <= str2[i]) {
            str2[i] -= '0';
        }
        str1[i] += str2[i];
        if(9 < str1[i]) {
            str1[i] -= 10;
            str1[i - 1] += 1;
        }
    }

    calc_get_number_string(str1, result);
}

static void calc_subtraction(char * text, char * num1, char * num2, bool negative_num1)
{
    if(NULL == text) return;

    if(0 == strcmp(num1, num2)) {
        text[0] = '0';
        return;
    }

    char * result = text;

    /*integer 1 - 10, decimal 11 - 20*/
    uint8_t str1[21] = {0};
    uint8_t str2[21] = {0};

    if(calc_copy_str_to_num(str1, num1)) {
        if(false == negative_num1) {
            text[0] = '-';
            result = &text[1];
        }
        strncpy(result, num2, CALC_MAX_TEXT_SIZE - 1);
        return;
    }

    calc_copy_str_to_num(str2, num2);

    bool negative = false;
    for(uint8_t i = 0; i < 21; i++) {
        if(str1[i] > str2[i]) {
            break;
        } else if(str1[i] < str2[i]) {
            char str_tmp[21] = {0};
            memcpy(str_tmp, str1, 21);
            memcpy(str1, str2, 21);
            memcpy(str2, str_tmp, 21);
            negative = true;
            break;
        }
    }

    if(negative != negative_num1) {
        text[0] = '-';
        result = &text[1];
    }

    uint8_t one_or_zero = 0;
    for(int8_t i = 20; i >= 0; i--) {
        str2[i] += one_or_zero;
        if(str1[i] < str2[i]) {
            str1[i] = str1[i] + 10 - str2[i];
            one_or_zero = 1;
        } else {
            str1[i] -= str2[i];
            one_or_zero = 0;
        }
    }

    calc_get_number_string(str1, result);
}

static uint8_t * calc_mul_copy_str_to_num(char * str, uint8_t * num_len, int8_t * num_e_num)
{
    uint8_t * num;

    uint8_t len = strlen(str);
    uint8_t p_pos = 0;
    uint8_t e_pos = 0;
    int8_t e_num = 0;
    calc_get_number_pos(str, &p_pos, &e_pos, &e_num);
    if(0 != e_num) {
        if(0 != p_pos) {
            len = e_pos - 1;
            e_num -= e_pos - 2;
        } else {
            /*no decimal*/
            len = 1;
        }
        num = (uint8_t *)lv_mem_alloc(len);
        num[0] = str[0] - '0';
        if(0 != p_pos) {
            uint8_t index = 1;
            for(uint8_t i = 2; i < e_pos; i++) {
                num[index++] = str[i] - '0';
            }
        }
    } else {
        if(0 == p_pos) {
            /*no decimal*/
            for(uint8_t i = len - 1; i > 0; i++) {
                if(0 < str[i]) {
                    e_num = len - i - 1;
                    len = i + 1;
                    break;
                }
            }
            num = (uint8_t *)lv_mem_alloc(len);
            for(uint8_t i = 0; i < len; i++) {
                num[i] = str[i] - '0';
            }
        } else {
            if('0' == str[0]) {
                /*0.xxxxx*/
                e_num = (len - 2) * (-1);
                uint8_t start_index = 2;
                for(uint8_t i = start_index; i < len; i++) {
                    if('0' < str[i]) {
                        start_index = i;
                        break;
                    }
                }
                len -= start_index;
                num = (uint8_t *)lv_mem_alloc(len);
                for(uint8_t i = 0; i < len; i++) {
                    num[i] = str[i + start_index] - '0';
                }
            } else {
                /*xxx.xxx*/
                num = (uint8_t *)lv_mem_alloc(len - 1);
                uint8_t index = 0;
                for(uint8_t i = 0; i < len; i++) {
                    if(p_pos == i) {
                        continue;
                    }
                    num[index++] = str[i] - '0';
                }
                len--;
                e_num = (len - p_pos) * (-1);
            }
        }
    }

    if(num_len) {
        *num_len = len;
    }
    if(num_e_num) {
        *num_e_num = e_num;
    }

    return num;
}

static void calc_result_round(uint8_t * str)
{
    for(uint8_t i = 20; i > 0; i--) {
        str[i]++;
        if(9 < str[i]) {
            str[i] -= 10;
        } else {
            break;
        }
    }
}

static void calc_multiplication(char * text, char * num1, char * num2, bool negative)
{
    char * result = text;
    if(negative) {
        result[0] = '-';
        result = &result[1];
    }

    uint8_t len1;
    uint8_t len2;
    int8_t e_num1;
    int8_t e_num2;
    uint8_t * str1 = calc_mul_copy_str_to_num(num1, &len1, &e_num1);
    uint8_t * str2 = calc_mul_copy_str_to_num(num2, &len2, &e_num2);
    uint8_t len = len1 + len2;
    int8_t e_num = e_num1 + e_num2;
    uint16_t * number = (uint16_t *)lv_mem_alloc(len * sizeof(uint16_t));
    memset(number, 0, len * sizeof(uint16_t));

    int8_t i;
    for(i = len2 - 1; i >= 0; i--) {
        for(int8_t j = len1 - 1; j >= 0; j--) {
            number[i + j + 1] += str2[i] * str1[j];
        }
    }
    lv_mem_free(str1);
    lv_mem_free(str2);
    for(i = len - 1; i > 0; i--) {
        if(9 < number[i]) {
            number[i - 1] += number[i] / 10;
            number[i] = number[i] % 10;
        }
    }

    uint8_t start_index = 0;
    if(0 == number[0]) {
        start_index = 1;
    }
    if(0 <= e_num) {
        /*xxx000*/
        uint8_t index = 0;
        for(i = start_index; i < len; i++) {
            result[index++] = number[i] + '0';
        }
        for(i = 0; i < e_num; i++) {
            result[index++] = '0';
        }
    } else {
        e_num = -e_num;
        uint8_t real_len;
        if(0 == number[0]) {
            real_len = len - 1;
        } else {
            real_len = len;
        }
        uint8_t str[21] = {0}; /*integer 1 - 10, decimal 11 - 20*/
        if(real_len <= e_num) {
            /*0.xxxxx*/
            uint8_t zero_len_after_point = e_num - real_len;
            if(10 < zero_len_after_point) {
                printf("%s: error, e_num %d > (real_len %d + 10)\n", __FUNCTION__, e_num, real_len);
                lv_mem_free(number);
                return;
            }
            uint8_t left_len = 10 - zero_len_after_point;
            uint8_t index = 11 + zero_len_after_point;
            if(real_len <= left_len) {
                for(i = 0; i < real_len; i++) {
                    str[index + i] = (uint8_t)number[start_index + i];
                }
            } else {
                for(i = 0; i < left_len; i++) {
                    str[index + i] = (uint8_t)number[start_index + i];
                }
                if(5 <= number[start_index + left_len]) {
                    /*round*/
                    calc_result_round(str);
                }
            }
        } else {
            /*xxx.xxx*/
            uint8_t int_len = real_len - e_num;
            if(10 < int_len) {
                printf("%s: error, real_len %d > (e_num %d + 10)\n", __FUNCTION__, real_len, e_num);
                lv_mem_free(number);
                return;
            }
            uint8_t dec_len = (e_num > 10) ? 10 : e_num;
            for(i = 0; i < int_len; i++) {
                str[11 - int_len + i] = (uint8_t)number[start_index + i];
            }
            uint8_t decimal_index = start_index + int_len;
            for(i = 0; i < dec_len; i++) {
                str[11 + i] = (uint8_t)number[decimal_index + i];
            }
            if((10 < e_num) && (5 <= number[decimal_index + 10])) {
                /*round*/
                calc_result_round(str);
            }
        }
        calc_get_number_string(str, result);
    }
    lv_mem_free(number);
}

static int8_t calc_div_compare(uint8_t * str1, uint8_t * str2, uint8_t str2_len)
{
    for(uint8_t i = 0; i < str2_len; i++) {
        if(str1[i] > str2[i]) {
            return 1;
        } else if(str1[i] < str2[i]) {
            return -1;
        }
    }

    return 0;
}

static uint8_t * calc_dividend_copy_str_to_num(char * str,
        uint8_t divisor_len,
        uint8_t * len1,
        uint8_t * num_len,
        int8_t * num_e_num)
{
    uint8_t * num;

    uint8_t len = strlen(str);
    uint8_t p_pos = 0;
    uint8_t e_pos = 0;
    uint8_t real_len;
    int8_t e_num = 0;
    calc_get_number_pos(str, &p_pos, &e_pos, &e_num);
    if(0 != e_num) {
        if(0 != p_pos) {
            len = e_pos - 1;
            e_num -= e_pos - 2;
        } else {
            /*no decimal*/
            len = 1;
        }
        real_len = len;
        if(len <= divisor_len) {
            len = divisor_len + 1;
        }
        num = (uint8_t *)lv_mem_alloc(len);
        memset(num, 0, len);
        num[0] = str[0] - '0';
        if(0 != p_pos) {
            uint8_t index = 1;
            for(uint8_t i = 2; i < e_pos; i++) {
                num[index++] = str[i] - '0';
            }
        }
    } else {
        if(0 == p_pos) {
            /*no decimal*/
            for(uint8_t i = len - 1; i > 0; i++) {
                if(0 < str[i]) {
                    e_num = len - i - 1;
                    len = i + 1;
                    break;
                }
            }
            real_len = len;
            e_pos = len;
            if(len <= divisor_len) {
                len = divisor_len + 1;
            }
            num = (uint8_t *)lv_mem_alloc(len);
            memset(num, 0, len);
            for(uint8_t i = 0; i < e_pos; i++) {
                num[i] = str[i] - '0';
            }
        } else {
            if('0' == str[0]) {
                /*0.xxxxx*/
                e_num = (len - 2) * (-1);
                uint8_t start_index = 2;
                for(uint8_t i = start_index; i < len; i++) {
                    if('0' < str[i]) {
                        start_index = i;
                        break;
                    }
                }
                len -= start_index;
                real_len = len;
                e_pos = len;
                if(len <= divisor_len) {
                    len = divisor_len + 1;
                }
                num = (uint8_t *)lv_mem_alloc(len);
                memset(num, 0, len);
                for(uint8_t i = 0; i < e_pos; i++) {
                    num[i] = str[i + start_index] - '0';
                }
            } else {
                /*xxx.xxx*/
                e_num = (len - p_pos - 1) * (-1);
                e_pos = len;
                len--;
                real_len = len;
                if(len <= divisor_len) {
                    len = divisor_len + 1;
                }
                num = (uint8_t *)lv_mem_alloc(len);
                memset(num, 0, len);
                uint8_t index = 0;
                for(uint8_t i = 0; i < e_pos; i++) {
                    if(p_pos == i) {
                        continue;
                    }
                    num[index++] = str[i] - '0';
                }
            }
        }
    }

    if(len1) {
        *len1 = real_len;
    }
    if(num_len) {
        *num_len = len;
    }
    if(num_e_num) {
        *num_e_num = e_num;
    }

    return num;
}

static void calc_division(char * text, char * num1, char * num2, bool negative)
{
    char * result = text;
    if(negative) {
        result[0] = '-';
        result = &result[1];
    }

    uint8_t len1;
    uint8_t len2;
    int8_t e_num1;
    int8_t e_num2;
    uint8_t * str2 = calc_mul_copy_str_to_num(num2, &len2, &e_num2);
    uint8_t str1_len;
    uint8_t * str1 = calc_dividend_copy_str_to_num(num1, len2, &len1, &str1_len, &e_num1);
    int8_t e_num = e_num1 - e_num2;
    int8_t i;
    uint8_t str[22] = {0};
    int8_t str_index = 10 - len1 + len2 - e_num;
    if((1 == len2) && (1 == str2[0])) {
        memcpy(&str[str_index], str1, len1);
    } else {
        uint8_t divisor;
        uint8_t one_or_zero = 0;
        while(1) {
            if(0 > calc_div_compare(str1, str2, len2)) {
                /*if str1:xx < str2:xx, str1:xxx - str2:xx*/
                str1[1] += str1[0] * 10;
                memmove(str1, &str1[1], str1_len - 1);
                str1[str1_len - 1] = 0;
                str_index++;
                if(21 < str_index) {
                    if(5 <= str[21]) {
                        /*round*/
                        calc_result_round(str);
                    }
                    break;
                }
            }
            for(i = len2 - 1; i >= 0; i--) {
                divisor = str2[i] + one_or_zero;
                if(str1[i] < divisor) {
                    one_or_zero = 1;
                    str1[i] += 10;
                } else {
                    one_or_zero = 0;
                }
                str1[i] -= divisor;
            }
            for(i = 0; i < str1_len; i++) {
                if(0 < str1[i]) {
                    break;
                }
            }
            str[str_index]++;
            if(str1_len == i) {
                break;
            } else {
                if(0 < i) {
                    uint8_t move_len = str1_len - i;
                    memmove(str1, &str1[i], move_len);
                    memset(&str1[move_len], 0, i);
                    str_index += i;
                    if(21 < str_index) {
                        if(5 <= str[21]) {
                            /*round*/
                            calc_result_round(str);
                        }
                        break;
                    }
                }
            }
        }
    }
    calc_get_number_string(str, result);
    lv_mem_free(str1);
    lv_mem_free(str2);
}

static void calc_number_round(double number, char * text)
{
    int len = 0;
    int precision = MAX_PRECISION;
    len = snprintf(text, MAX_TOKEN_SIZE + EXT_TOKEN_SIZE - 1, "%.*g", precision, number);
    while(len > (MAX_TOKEN_SIZE - 1) && precision > 0) {
        len = snprintf(text, MAX_TOKEN_SIZE + EXT_TOKEN_SIZE - 1, "%.*g", --precision, number);
    }
}

static void calc_get_result(char * fac1, char * fac2)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(NULL == ext) {
        printf("%s: ext NULL\n", __FUNCTION__);
        return;
    }

    double fval1  = 0;
    double fval2  = 0;
    double resval = 0;

    fval1 = atof(fac1);
    fval2 = atof(fac2);

    if(strcmp(ext->oper, CALC_PLUS) == 0) {
        resval = fval1 + fval2;
    } else if(strcmp(ext->oper, CALC_MINUS) == 0) {
        resval = fval1 - fval2;
    } else if(strcmp(ext->oper, CALC_MUL) == 0) {
        if(fval1 == 0 || fval1 == -0 || fval2 == 0) {
            calc_label_set_text(ext, "0");
            return;
        }
        resval = fval1 * fval2;
    } else if(strcmp(ext->oper, CALC_DIV) == 0) {
        if(fval2 == 0) {
            calc_label_set_text(ext, (char *)lv_lang_get_text(WATCH_TEXT_ID_CALC_INF));
            return;
        } else if(fval1 == 0 || fval1 == -0) {
            calc_label_set_text(ext, "0");
            return;
        }
        resval = fval1 / fval2;
    } else {
        printf("ui error, %s in %s\n", ext->oper, __FUNCTION__);
        calc_label_set_text(ext, "");
        return;
    }

    char * text = (char *)lv_mem_alloc(CALC_MAX_TEXT_SIZE);
    calc_number_round(resval, text);
    if(strchr(text, 'e')) {
        printf("%s: scientific notation\n", __FUNCTION__);
        calc_label_set_text(ext, text);
        lv_mem_free(text);
        return;
    }

    memset(text, 0, CALC_MAX_TEXT_SIZE);

    if(strcmp(ext->oper, CALC_PLUS) == 0) {
        if('-' == fac1[0]) {
            if('-' == fac2[0]) {
                calc_addition(text, &fac1[1], &fac2[1], true);
            } else {
                calc_subtraction(text, &fac1[1], fac2, true);
            }
        } else {
            if('-' == fac2[0]) {
                calc_subtraction(text, fac1, &fac2[1], false);
            } else {
                calc_addition(text, fac1, fac2, false);
            }
        }
    } else if(strcmp(ext->oper, CALC_MINUS) == 0) {
        if('-' == fac1[0]) {
            if('-' == fac2[0]) {
                calc_subtraction(text, &fac1[1], &fac2[1], true);
            } else {
                calc_addition(text, &fac1[1], fac2, true);
            }
        } else {
            if('-' == fac2[0]) {
                calc_addition(text, fac1, &fac2[1], false);
            } else {
                calc_subtraction(text, fac1, fac2, false);
            }
        }
    } else if(strcmp(ext->oper, CALC_MUL) == 0) {
        if('-' == fac1[0]) {
            if('-' == fac2[0]) {
                calc_multiplication(text, &fac1[1], &fac2[1], false);
            } else {
                calc_multiplication(text, &fac1[1], fac2, true);
            }
        } else {
            if('-' == fac2[0]) {
                calc_multiplication(text, fac1, &fac2[1], true);
            } else {
                calc_multiplication(text, fac1, fac2, false);
            }
        }
    } else if(strcmp(ext->oper, CALC_DIV) == 0) {
        if('-' == fac1[0]) {
            if('-' == fac2[0]) {
                calc_division(text, &fac1[1], &fac2[1], false);
            } else {
                calc_division(text, &fac1[1], fac2, true);
            }
        } else {
            if('-' == fac2[0]) {
                calc_division(text, fac1, &fac2[1], true);
            } else {
                calc_division(text, fac1, fac2, false);
            }
        }
    }

    calc_label_set_text(ext, text);
    lv_mem_free(text);
}

static void calc_clear_btn_rel_action(lv_obj_t * btn)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(ext->pr_lost) {
        ext->pr_lost = false;
        return;
    }

    lv_obj_set_hidden(ext->cont_circle, true);

    const char * text = lv_label_get_text(ext->label);
    const char * inf = lv_lang_get_text(WATCH_TEXT_ID_CALC_INF);
    if(ext->oper == NULL) {
        if(strcmp(text, inf) == 0 || strchr(text, 'e')) {
            calc_label_set_text(ext, "");
            ext->finish_flag = false;
            ext->oper = NULL;
            ext->input_len = 0;
            ext->point = false;
            return;
        }
    }

    calc_label_del_char(ext);
    uint8_t len = strlen(text);
    if(ext->oper && ext->point == false && ext->input_len == 0) {
        ext->oper = NULL;
        char * point = strchr(text, '.');
        if(point) {
            ext->point = true;
            ext->input_len = strlen(point) - 1;
        } else {
            ext->input_len = len;
            if(text[0] == '-') {
                ext->input_len--;
            }
        }
    } else if(ext->point && ext->input_len == 0) {
        ext->point = false;
        if(ext->oper) {
            uint8_t len_sign = strlen(ext->oper);
            uint8_t i = len - 1;
            for(; i > 0; i--) {
                if(strncmp(&text[i + 1 - len_sign], ext->oper, len_sign) == 0) {
                    break;
                }
            }
            ext->input_len = len - 1 - i;
        } else {
            ext->input_len = len;
            if(text[0] == '-') {
                ext->input_len--;
            }
        }
    } else if(ext->input_len > 0) {
        ext->input_len--;
    }
}

static void calc_btn_pr_action(lv_obj_t * btn)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    lv_obj_align(ext->cont_circle, btn, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_hidden(ext->cont_circle, false);
}

static void calc_clear_btn_longpress_action(lv_obj_t * btn)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(ext->pr_lost) return;
    calc_label_set_text(ext, "");
    ext->oper = NULL;
    ext->finish_flag = false;
    ext->point = false;
    ext->input_len = 0;
}

static void calc_btn_pressing_action(lv_obj_t * btn)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(ext->pr_lost) return;
    lv_point_t point;
    lv_indev_get_point(lv_indev_get_act(), &point);
    if(lv_indev_get_obj_act() != lv_indev_search_obj(ext->lv_watch_obj.activity_obj, &point)) {
        ext->pr_lost = true;
        lv_obj_set_hidden(ext->cont_circle, true);
    }
}

static void calc_clear_event_cb(lv_obj_t * obj, lv_event_t event)
{
    if(LV_EVENT_RELEASED == event) {
        calc_clear_btn_rel_action(obj);
    } else if(LV_EVENT_PRESSED == event) {
        calc_btn_pr_action(obj);
    } else if(LV_EVENT_LONG_PRESSED == event) {
        calc_clear_btn_longpress_action(obj);
    } else if(LV_EVENT_PRESSING == event) {
        calc_btn_pressing_action(obj);
    }
}

static void calc_set_oper(char * ptr)
{
    char * fac1 = NULL;
    char * fac2 = NULL;
    char * temp = NULL;
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    const char * text = lv_label_get_text(ext->label);
    uint8_t len = strlen(text);

    const char * inf = lv_lang_get_text(WATCH_TEXT_ID_CALC_INF);
    if(strcmp(text, inf) == 0) {
        calc_label_set_text(ext, "");
        ext->finish_flag = false;
        ext->oper = NULL;
        ext->input_len = 0;
        ext->point = false;
        len = 0;
    } else if(strcmp(text, "-") == 0 && strcmp(ptr, CALC_MINUS) == 0) {
        return;
    }

    if(strcmp(ptr, CALC_PLUS) == 0 || strcmp(ptr, CALC_MINUS) == 0 || strcmp(ptr, CALC_MUL) == 0 || strcmp(ptr, CALC_DIV) == 0) {
        calc_add_oper(ptr);
    } else if(strcmp(ptr, CALC_EQUALS) == 0) {
        if(ext->oper && ((ext->point == true) || (ext->point == false && ext->input_len > 0))) {
            if(text[len - 1] == '.') {
                calc_label_del_char(ext);
            }
            calc_label_add_text(ext, ptr);

            text = lv_label_get_text(ext->label);
            temp = (char *)text;
            if(temp[0] == '-') {
                temp = &temp[1];
            }
            char * sn = strchr(text, 'e');
            if(sn) {
                temp = &sn[2];
            }
            strtok(temp, ext->oper);
            fac2 = strtok(NULL, "=");
#if CALC_LATIN_SYMBOL_MUL_AND_DIV_SUP != 0
            if(strcmp(ext->oper, CALC_MUL) == 0 || strcmp(ext->oper, CALC_DIV) == 0) {
                fac2 = &fac2[1];
            }
#endif
            fac1 = (char *)text;
            calc_get_result(fac1, fac2);

            ext->oper = NULL;
            ext->finish_flag = true;
            text = lv_label_get_text(ext->label);
            char * point = strchr(text, '.');
            if(point) {
                ext->point = true;
                ext->input_len = strlen(point) - 1;
            } else {
                ext->point = false;
                ext->input_len = strlen(text);
                if(text[0] == '-') {
                    ext->input_len--;
                }
            }
        }
    } else if(strcmp(ptr, ".") == 0) {
        if(len == 0 || ext->finish_flag || (ext->oper == NULL && strchr(text, 'e'))) {
            calc_label_set_text(ext, "0.");
            ext->finish_flag = false;
            ext->point = true;
            ext->input_len = 0;
        } else if(strcmp(text, "-") == 0) {
            calc_label_set_text(ext, "-0.");
            ext->finish_flag = false;
            ext->point = true;
            ext->input_len = 0;
        } else if(ext->point == false) {
            if(ext->oper && ext->input_len == 0) {
                calc_label_add_text(ext, "0.");
            } else
                calc_label_add_text(ext, ptr);
            ext->point = true;
            ext->input_len = 0;
        }
    } else {
        if(ext->finish_flag || (ext->oper == NULL && strchr(text, 'e'))) {
            calc_label_set_text(ext, "");
            ext->input_len = 0;
            ext->finish_flag = false;
            ext->point = false;
        } else if(ext->input_len == 10) {
            lv_obj_set_hidden(ext->cont_warn, false);
            if(ext->warn_task_p)
                lv_task_reset(ext->warn_task_p);
            else
                ext->warn_task_p = lv_task_create(calc_warn_task,
                                                  CALC_WARNING_TIME_OUT,
                                                  LV_TASK_PRIO_MID,
                                                  NULL);
            return;
        }
        text = lv_label_get_text(ext->label);
        uint8_t text_len = strlen(text);
        if((ext->input_len == 1)
                && (text[text_len - 1] == '0')
                && (ext->point == false)) {
            if(ptr[0] == '0')
                return;
            else {
                calc_label_del_char(ext);
                ext->input_len = 0;
            }
        }

        calc_label_add_text(ext, ptr);
        ext->input_len++;
    }
}

static void calc_num_btn_rel_action(lv_obj_t * btn)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(ext->pr_lost) {
        ext->pr_lost = false;
        return;
    }

    lv_obj_user_data_t * user_data = lv_obj_get_user_data_ptr(btn);
    const char * btn_str[] = {"7", "8", "9", CALC_DIV,
                              "4", "5", "6", CALC_MUL,
                              "1", "2", "3", CALC_MINUS,
                              ".", "0", CALC_EQUALS, CALC_PLUS
                             };
    calc_set_oper((char *)btn_str[user_data->user_num]);
    lv_obj_set_hidden(ext->cont_circle, true);
}

static void calc_num_event_cb(lv_obj_t * obj, lv_event_t event)
{
    if(LV_EVENT_RELEASED == event) {
        calc_num_btn_rel_action(obj);
    } else if(LV_EVENT_PRESSED == event) {
        calc_btn_pr_action(obj);
    } else if(LV_EVENT_PRESSING == event) {
        calc_btn_pressing_action(obj);
    }
}

static void calc_prepare_destory(lv_obj_t * activity_obj)
{
    lv_calculator_obj_ext_t * ext = calc_get_ext();
    if(ext->warn_task_p) {
        lv_task_del(ext->warn_task_p);
    }
    lv_watch_png_cache_all_free();
}

static lv_obj_t * calc_create(lv_obj_t * activity_obj)
{
    uint8_t i = 0;
    lv_obj_t * btn[16];
    lv_obj_t * img[16];
    const void * num[] = {
        (void *)ICON_7,
        (void *)ICON_8,
        (void *)ICON_9,
        (void *)ICON_DIV,
        (void *)ICON_4,
        (void *)ICON_5,
        (void *)ICON_6,
        (void *)ICON_MUL,
        (void *)ICON_1,
        (void *)ICON_2,
        (void *)ICON_3,
        (void *)ICON_MINUS,
        (void *)ICON_DOT,
        (void *)ICON_0,
        (void *)ICON_EQUAL,
        (void *)ICON_PLUS,
    };

    if(activity_obj == NULL) {
        lv_watch_activity_ext_t activity_ext;
        memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
        activity_ext.actId = ACT_ID_CALCULATOR;
        activity_ext.create = calc_create;
        activity_ext.prepare_destory = calc_prepare_destory;
        activity_obj = lv_watch_creat_activity_obj(&activity_ext);
        LV_ASSERT_MEM(activity_obj);
        if(activity_obj == NULL) return NULL;
    }

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    if(obj == NULL) return NULL;
    lv_watch_obj_set_anim_mode(obj, LV_WATCH_ANIM_HOR_RIGHT_HIDE);

    lv_calculator_obj_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_calculator_obj_ext_t));
    LV_ASSERT_MEM(ext);
    if(ext == NULL) return NULL;
    ext->cont_circle = NULL;
    ext->cont_warn = NULL;
    ext->finish_flag = false;
    ext->oper = NULL;
    ext->input_len = 0;
    ext->point = false;
    ext->pr_lost = false;

    ext->page = lv_page_create(obj, NULL);
    lv_obj_set_size(ext->page, LV_HOR_RES * 4 / 5 - 10, LV_VER_RES / 5 + 2);

    lv_obj_add_style(ext->page, LV_PAGE_PART_BG, &lv_style_transp_tight);
    lv_obj_add_style(ext->page, LV_PAGE_PART_SCROLLABLE, &lv_style_transp_tight);
    lv_page_set_scrollbar_mode(ext->page, LV_SCROLLBAR_MODE_OFF);
    lv_page_set_scrollable_fit2(ext->page, LV_FIT_MAX, LV_FIT_NONE);
    lv_obj_align(ext->page, obj, LV_ALIGN_IN_TOP_LEFT, 3, 4);
    ext->label = lv_label_create(ext->page, NULL);
    lv_obj_set_base_dir(ext->label, LV_BIDI_DIR_LTR);
    lv_label_set_long_mode(ext->label, LV_LABEL_LONG_EXPAND);
    lv_obj_set_style_local_text_font(ext->label,
                                     LV_LABEL_PART_MAIN,
                                     LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_BIG);
    calc_label_set_text(ext, "");
    lv_watch_obj_add_element(ext->page);

    ext->cont_circle = lv_cont_create(obj, NULL);
    lv_obj_set_style_local_bg_opa(ext->cont_circle,
                                  LV_CONT_PART_MAIN,
                                  LV_STATE_DEFAULT,
                                  LV_OPA_30);
    lv_obj_set_style_local_radius(ext->cont_circle,
                                  LV_CONT_PART_MAIN,
                                  LV_STATE_DEFAULT,
                                  100);
    lv_obj_set_style_local_bg_color(ext->cont_circle,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_WHITE);
    lv_obj_set_style_local_border_opa(ext->cont_circle,
                                      LV_CONT_PART_MAIN,
                                      LV_STATE_DEFAULT,
                                      LV_OPA_0);
    lv_obj_set_size(ext->cont_circle, 48, 48);
    lv_obj_set_hidden(ext->cont_circle, true);

    lv_obj_t * clear_btn = lv_btn_create(obj, NULL);
    lv_obj_t * clear_img = lv_img_create(clear_btn, NULL);
    lv_img_set_src(clear_img, ICON_BACKSPACE);
    lv_obj_set_size(clear_btn, LV_HOR_RES / 5, LV_VER_RES / 5);
    lv_obj_add_style(clear_btn, LV_BTN_PART_MAIN, &lv_style_transp_tight);
    lv_obj_align(clear_btn, ext->page, LV_ALIGN_OUT_RIGHT_MID, 0, 0);
    lv_obj_set_event_cb(clear_btn, calc_clear_event_cb);
    lv_watch_obj_add_element(clear_btn);

    lv_obj_t * cont = lv_cont_create(obj, NULL);
    lv_obj_set_size(cont, LV_HOR_RES, LV_VER_RES * 4 / 5);
    lv_cont_set_layout(cont, LV_LAYOUT_GRID);
    lv_obj_align(cont, obj, LV_ALIGN_IN_BOTTOM_MID, 0, -2);
    lv_watch_obj_add_element(cont);

    /*set cont style*/
    lv_obj_set_style_local_bg_opa(cont,
                                  LV_CONT_PART_MAIN,
                                  LV_STATE_DEFAULT,
                                  LV_OPA_10);
    lv_obj_set_style_local_pad_left(cont,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    2);
    lv_obj_set_style_local_pad_right(cont,
                                     LV_CONT_PART_MAIN,
                                     LV_STATE_DEFAULT,
                                     2);
    lv_obj_set_style_local_pad_top(cont,
                                   LV_CONT_PART_MAIN,
                                   LV_STATE_DEFAULT,
                                   0);
    lv_obj_set_style_local_pad_bottom(cont,
                                      LV_CONT_PART_MAIN,
                                      LV_STATE_DEFAULT,
                                      0);
    lv_obj_set_style_local_pad_inner(cont,
                                     LV_CONT_PART_MAIN,
                                     LV_STATE_DEFAULT,
                                     0);

    for(i = 0; i < 16; i++) {
        btn[i] = lv_btn_create(cont, NULL);
        lv_obj_set_size(btn[i], (LV_HOR_RES - 6) / 4, LV_VER_RES / 5);
        img[i] = lv_img_create(btn[i], NULL);
        lv_img_set_src(img[i], num[i]);
        lv_obj_add_style(btn[i], LV_BTN_PART_MAIN, &lv_style_transp_tight);
        lv_obj_set_event_cb(btn[i], calc_num_event_cb);
        lv_obj_user_data_t * user_data = lv_obj_get_user_data_ptr(btn[i]);
        user_data->user_num = i;
        lv_watch_obj_add_element(btn[i]);
    }

    ext->cont_warn = lv_cont_create(obj, NULL);
    lv_cont_set_fit(ext->cont_warn, true);
    lv_obj_t * label_warn = lv_label_create(ext->cont_warn, NULL);
    lv_label_set_text_id(label_warn, WATCH_TEXT_ID_CALC_WARNING);
    lv_obj_set_style_local_bg_opa(ext->cont_warn,
                                  LV_CONT_PART_MAIN,
                                  LV_STATE_DEFAULT,
                                  LV_OPA_30);
    lv_obj_set_style_local_text_font(ext->cont_warn,
                                     LV_CONT_PART_MAIN,
                                     LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_SMALL);
    lv_obj_set_style_local_text_color(ext->cont_warn,
                                      LV_CONT_PART_MAIN,
                                      LV_STATE_DEFAULT,
                                      LV_COLOR_YELLOW);
    lv_obj_set_hidden(ext->cont_warn, true);
    lv_obj_align(ext->cont_warn, obj, LV_ALIGN_IN_TOP_MID, 0, LV_VER_RES / 2 + 10);
    ext->warn_task_p = NULL;

    return obj;
}

#endif /*USE_LV_WATCH_CALCULATOR*/
